/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.core.contextmanager;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jdpf.core.contextmanager.services.View;
import org.jdpf.core.kernel.elements.ITransition;
import org.jdpf.core.kernel.elements.Net;
import org.jdpf.core.kernel.elements.Token;
import org.jdpf.core.kernel.exceptions.BundleNotFoundException;
import org.jdpf.core.kernel.exceptions.CoreException;
import org.jdpf.core.kernel.execution.NetValidatorExecutor;
import org.jdpf.core.kernel.execution.Task;
import org.jdpf.core.kernel.logging.ILoggable;
import org.jdpf.core.kernel.logging.ILogger;
import org.jdpf.core.kernel.logging.LoggerFactory;
import org.jdpf.core.kernel.plugins.BundleToolBox;
import org.jdpf.core.kernel.plugins.IPluginsFrameworkConstants;
import org.jdpf.core.netloader.service.NetLoadingService;
import org.jdpf.core.netvalidator.service.NetValidationService;
import org.jdpf.core.paramloader.service.ParamLoadingService;
import org.jdpf.core.plugins.parameters.Parameters;
import org.jdpf.core.plugins.registration.RegistrationService;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;

public class ContextManager {

	private static final ILogger logger = LoggerFactory.getDefault()
			.getLogger();

	private View view;
	private String xmlNetFile, xmlParamFile, netFile;
	private NetLoadingService netParser;
	private NetValidationService netValidator;
	private ParamLoadingService paramParser;
	private Token token;
	private TaskManager taskManager;
	private Task currentTask;
	private Hashtable<String, Net> nets;
	private Net currentNet;

	private String workspacePath;

	public ContextManager() {
		workspacePath = null;
		currentNet = null;
		token = null;
		nets = new Hashtable<String, Net>();

		this.netParser=(NetLoadingService) BundleToolBox.getDefault().getService("org.jdpf.core.netloader.NetLoader");
		logger.log(this, this.netParser!=null?"Net parser loaded.":"Unable to load Net parser.");

		this.netValidator=(NetValidationService) BundleToolBox.getDefault().getService("org.jdpf.core.netvalidator.NetValidator");
		logger.log(this, this.netValidator!=null?"Net validator loaded.":"Unable to load Net validator.");

		this.paramParser=(ParamLoadingService) BundleToolBox.getDefault().getService("org.jdpf.core.paramloader.ParamLoader");
		logger.log(this, this.paramParser!=null?"Param parser loaded.":"Unable to load Param parser.");
	}

	/**
	 * Load a net that can be used in the framework.
	 * 
	 * @param netFile -
	 *            the absolute path of the xml file which describes the net.
	 * @return the complete net as described in the xml file.
	 */
	public Net loadNet(String netFile) {
		if (netParser instanceof ILoggable)
			((ILoggable) netParser).enableLogging(true);

		Net n = null;
		if (netFile.endsWith(".xml")) {
			try {
				n = netParser.load(netFile);
				// net validation
				netValidator.validateNetStatically(n);
				netValidator
						.validateNetDynamically(new NetValidatorExecutor(n));
			} catch (Exception e) {
				if (e.getMessage() != null)
					logger.error(this, e.getMessage());
				else
					logger.error(this, e.getCause().getMessage());
			}
			if (!nets.containsKey(netFile) && n != null)
				nets.put(netFile, n);
			currentNet = n;
		} else {
			logger.error(this, "The system is unable to handle the net file "
					+ netFile + " due to bad file extension.");
		}
		return n;
	}

	/**
	 * Load a new task in the framework.
	 * <p>
	 * The task loading procedure is made up of two different steps:
	 * <ul>
	 * <li>the generation of a net;</li>
	 * <li>the generation of the data structure to store parameters of the
	 * task.</li>
	 * </ul>
	 * While loading all parameters for the given task, the system also controls
	 * that all is valid and can be run correctly.
	 * </p>
	 * 
	 * @param netFile -
	 *            a string representing the absolute path to xml file describing
	 *            the net.
	 * @param paramFile -
	 *            a string representing the absolute path to xml file describing
	 *            the parameters for the specified net.
	 */
	public Task loadTask(String netFile, String paramFile) {
		Task t = new Task(netFile, paramFile);
		xmlParamFile = paramFile;

		Net n = loadNet(netFile);

		if (n != null) {
			loadParam(t);
			t.setValidity(true);
			t.setNet(n);
			if (n.getName() != null)
				t.setNetName(n.getName());
			else
			t.setNetName(t.getNetFile());
			if (token.getValueList().size()>0)
				t.setParam((Parameters) token.getValueList().get(0));
			else
				t.setValidity(false);
			try {
				if (t.isValid()) {
					t.checkParameters();
					t.setValidity(true);
				}
			} catch (Exception e) {
				t.setValidity(false);
				if (e.getMessage() != null)
					logger.error(this, e.getMessage());
				else if (e.getCause().getMessage() != null)
					logger.error(this, e.getCause().getMessage());
				else
					logger.error(this, "Nested exception!");
			}
		} else
			t.setValidity(false);
		currentTask = t;
		currentNet = null;
		token = null;
		xmlNetFile = null;
		xmlParamFile = null;
		return t;
	}

	public void loadParam(Task t) {
		if (xmlParamFile.endsWith(".xml")) {
			paramParser.load(xmlParamFile);
			token = paramParser.getParamToken();
			if(token.getValueList().size()>0){
				t.setTaskName((String) ((Parameters) token.getValueList().get(0))
						.getParameterValue("org.jdpf.core.contextmanager.Task",
								"TaskName"));
				t.setTaskLogging((String) ((Parameters) token.getValueList()
								.get(0)).getParameterValue(
								"org.jdpf.core.contextmanager.Task", "TaskLogging"));
			}

		} else {
			logger.error(this,
					"The system is unable to handle the parameters file "
							+ xmlParamFile + " due to bad file extension.");
		}
	}

	/**
	 * 
	 */
	public void runCurrentTask() {
		if (currentTask == null)
			logger.error(this, "No task selected");

		try {
			currentTask.start();
		} catch (CoreException e) {
			if (e.getMessage() != null)
				logger.error(this, e.getMessage());
			else
				logger.error(this, e.getCause().getMessage());
		}
	}

	public void addTaskToList(Task t, String project) {
		taskManager.addTask(t, project);
		currentTask = null;
	}

	public void pauseCurrentTask() {
		currentTask.pause();
	}

	public boolean isTaskPaused() {
		return currentTask.isPaused();
	}

	public void stopCurrentTask() {
		currentTask.stop();
	}

	public void chooseXMLNetFile(String file) {
		xmlNetFile = file;
		view.update();
	}

	public void chooseXMLParamFile(String file) {
		xmlParamFile = file;
		view.update();
	}

	public String getXMLNetFile() {
		return xmlNetFile;
	}

	public String getXMLParamFile() {
		return xmlParamFile;
	}

	public Net getNet(String netFile) {
		if (nets.containsKey(netFile))
			return nets.get(netFile);
		return null;
	}

	public Token getToken() {
		return token;
	}

	public List<Task> getTasks(String netFile) {
		return taskManager.getTasks(netFile);
	}

	public Task getTask(String netFile, String paramFile) {
		return taskManager.getTask(netFile, paramFile);
	}

	public List<String> getNetsFiles() {
		Set<String> set = nets.keySet();
		List<String> list = new ArrayList<String>(5);
		for (String s : set) {
			list.add(s);
		}
		return list;
	}

	public Hashtable<String, Net> getNets() {
		return nets;
	}

	public TaskManager getTaskManager() {
		return taskManager;
	}

	public void setTaskManager(TaskManager taskManager) {
		this.taskManager = taskManager;
		this.taskManager.setContextManager(this);
	}

	public Task getCurrentTask() {
		return currentTask;
	}

	public void setCurrentTask(Task currentTask) {
		this.currentTask = currentTask;

	}

	public void removeTask(Task t, String project) {
		taskManager.removeTask(t, project);
		Net n = t.getNet();
		if (!n.equals(getNet(t.getNetFile())))
			unregisterNetServices(n);
	}

	public void unregisterNetServices(Net n) {
		currentTask = null;
		currentNet = null;
		Map<String, String> deleting = new HashMap<String, String>();
		for (ITransition tr : n.getAllTransitions()) {
			try {
				String serviceId = (String) tr.getClass().getMethod(
						"getServiceId", null).invoke(tr, null);
				String pluginId = (String) tr.getClass().getMethod(
						"getPluginId", null).invoke(tr, null);
				deleting.put(serviceId, pluginId);
			} catch (Exception e) {
				logger.error(this, e.getCause().getMessage());
			}
		}
		n = null;
		// Unregester all services from.
		Iterator<String> iter = deleting.keySet().iterator();
		while (iter.hasNext()) {
			String serviceId = iter.next();
			String pluginId = deleting.get(serviceId);
			BundleToolBox btb = BundleToolBox.getDefault();
			try {
				Bundle b = btb.getBundle(pluginId);
				ServiceReference[] srs = b.getRegisteredServices();
				ServiceReference sr = null;
				for (int i = 0; i < srs.length; i++) {
					if (IPluginsFrameworkConstants.REGISTRATION.equals(srs[i]
							.getProperty(IPluginsFrameworkConstants.CATEGORY)))
						sr = srs[i];
				}
				RegistrationService rs = (RegistrationService) btb
						.getService(sr);
				rs.unregister(serviceId);
			} catch (BundleNotFoundException e) {
				logger.error(this, "Unable to find bundle '" + pluginId + "'.");
			}
		}
	}

	public String getCurrentNetFile() {
		return netFile;
	}

	public void setCurrentNetFile(String netFile) {
		this.netFile = netFile;
	}

	/**
	 * Add a new net in the ContextManager nets list.
	 * <p>
	 * The CntextManager nets list is used to keep a list of all loaded net in
	 * the framework but these are not used by task. It usefull to load nets
	 * before tasks because doing so the system can understand immediatelly if
	 * the loaded component is valid.
	 * </p>
	 * 
	 * @param netFile -
	 *            the absolute path of the xml file which describes the net.
	 * @return the new net loaded, <code>null</code> if a parsing error
	 *         occurs.
	 */
	public Net addNet(String netFile) {
		if (!nets.containsKey(netFile))
			return loadNet(netFile);
		return null;
	}

	/**
	 * Add a new net both in the ContextManager and down in the ITaskHandler.
	 * 
	 * @param netFile -
	 *            the absolute path of the xml file which describes the net.
	 */
	public boolean addNetToList(String netFile, String project) {
		Net n = this.addNet(netFile);
		if (n != null && n.isValid()) {
			taskManager.addNet(netFile, project);
			return true;
		} else
			this.removeNet(netFile,null);
		return false;
	}

	public void removeNet(String net, String project) {
		if (project != null)
			taskManager.removeNet(net, project);
		if (nets.containsKey(net))
			unregisterNetServices(nets.remove(net));
	}

	public void loadWorkspace() {
		currentNet = null;
		currentTask = null;
		netFile = null;
		Iterator<String> netsIter = nets.keySet().iterator();
		while (netsIter.hasNext()) {
			String net = netsIter.next();
			unregisterNetServices(nets.remove(net));
		}
		taskManager.restoreWorkspace();
	}

	public void registerView(View view) {
		this.view = view;
	}

	public void unregisterView() {
		this.view = null;
	}

	public void exit() {
		logger.log("Shutting down JDPF framework.");
		System.exit(0);
	}

	public Net getCurrentNet() {
		return currentNet;
	}

	public void setCurrentNet(Net currentNet) {
		this.currentNet = currentNet;
	}

	public void setCurrentParametersToken(Token token) {
		this.token = token;
	}

	public String getWorkspacePath() {
		return workspacePath;
	}

	public void setWorkspacePath(final URL workspacePath) {
		this.workspacePath = workspacePath.getPath().substring(1);
		this.workspacePath = this.workspacePath.substring(0, this.workspacePath
				.length() - 1);
		this.workspacePath = this.workspacePath
				.replace('/', File.separatorChar)
				+ File.separator;
		taskManager.setWorkspace(this.workspacePath);

	}

}
